/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ac.iie.di.ban.news.server.util;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 *
 * @author zwlin
 */
public class FileAppender {

	private static HashMap<String, FileAppender> appenderMap = new HashMap<>();

	public static synchronized void register(String name, FileAppender appender) {
		appenderMap.put(name, appender);
	}

	public static FileAppender getAppender(String name) {
		return appenderMap.get(name);
	}

	private static final String SPLITTER = ".";
	boolean activated;

	File cacheDir;
	File targetDir;
	String name;
	int maxSize;

	int size;
	int seq;

	FileWriter fw;
	ReentrantReadWriteLock lock;

	/**
	 * write data into cacheDir and move to targetDir when file closed.
	 *
	 * @param cacheDir not null
	 * @param targetDir null means no move
	 * @throws IOException
	 */
	public FileAppender(String cacheDir, String targetDir) throws IOException {
		this(cacheDir, targetDir, null);
	}

	public FileAppender(String cacheDir, String targetDir, String filename) throws IOException {
		this(cacheDir, targetDir, filename, 0);
	}

	public FileAppender(String cacheDir, String targetDir, String filename, int size) throws IOException {
		initArg(cacheDir, targetDir, filename, size);
		initDefault();
		passOldFiles();
	}

	private void initArg(String cacheDir, String targetDir, String filename, int size) {
		//get cache/target dirs
		this.cacheDir = new File(cacheDir);
		if (targetDir == null || targetDir.isEmpty()) {
			this.targetDir = null;
		} else {
			this.targetDir = new File(targetDir);
		}

		//build cache dir if possible
		if (!this.cacheDir.exists()) {
			if (!this.cacheDir.mkdirs()) {
				throw new Error("cannot mkdir dir path:" + cacheDir);
			}
		} else if (!this.cacheDir.isDirectory()) {
			throw new Error("dir path:" + cacheDir + " is not a directory.");
		}

		//build target dir if possible
		if (this.targetDir != null) {
			if (!this.targetDir.exists()) {
				if (!this.cacheDir.mkdirs()) {
					throw new Error("cannot mkdir dir path:" + cacheDir);
				}
			} else if (!this.targetDir.isDirectory()) {
				throw new Error("dir path:" + cacheDir + " is not a directory.");
			}
		}

		this.name = filename == null ? "dataFile" : filename;
		this.maxSize = size <= 0 ? 64 * 1024 * 1024 : size * 1024 * 1024;
	}

	private void initDefault() {
		size = 0;
		seq = 0;
		lock = new ReentrantReadWriteLock();
		activated = false;
	}

	private void passOldFiles() {
		String[] files = cacheDir.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name != null && name.startsWith(FileAppender.this.name);
			}
		});
		int max = -1;
		for (String f : files) {
			move(f);
			String[] dvd = f.split(SPLITTER);
			if (dvd.length == 2) {
				try {
					int v = Integer.parseInt(dvd[1].trim());
					if (v > max) {
						max = v;
					}
				} catch (Exception e) {
				}
			}
		}
		seq = max + 1;
	}

	public synchronized void start() throws IOException {
		if (!activated) {
			fw = new FileWriter(new File(cacheDir, name + SPLITTER + seq));
			activated = true;
		}
	}

	public synchronized void stop() throws IOException {
		if (activated) {
			activated = false;
			lock.writeLock().lock();
			try {
				if (fw != null) {
					fw.flush();
					fw.close();
				}
			} finally {
				lock.writeLock().unlock();
			}
		}
	}

	public void append(String str) throws IOException {
		if (!activated) {
			throw new Error("FileAppender is closed!");
		}
		if (size > maxSize) {
			changeFile();
		}
		lock.readLock().lock();
		try {
			size += str.length();
			fw.append(str).append('\n').flush();
		} finally {
			lock.readLock().unlock();
		}
	}

	private void changeFile() throws IOException {
		if (size > 0) {
			lock.writeLock().lock();
			try {
				fw.flush();
				fw.close();
				fw = new FileWriter(new File(cacheDir, name + SPLITTER + (++seq)));
				size = 0;
			} finally {
				lock.writeLock().unlock();
			}
			move(name + SPLITTER + (seq - 1));
		}
	}

	private void move(String filename) {
		if (new File(targetDir, filename).exists()) {
			int i = 0;
			while (new File(targetDir, filename + "(" + (++i) + ")").exists());
			new File(cacheDir, filename).renameTo(new File(targetDir, filename + "(" + i + ")"));
		} else {
			new File(cacheDir, filename).renameTo(new File(targetDir, filename));
		}
	}

}
